/*
 * iSGL3D: http://isgl3d.com
 *
 * Copyright (c) 2010-2011 Stuart Caunt
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 * 
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 * 
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 *
 */

#import "Isgl3dTutorial1View.h"
#import "Isgl3dDemoCameraController.h"

#import "Isgl3dPhysicsWorld.h"
#import "Isgl3dPhysicsObject3D.h"
#import "Isgl3dMotionState.h"

#include "btBulletDynamicsCommon.h"
#include "btBox2dShape.h"
#include "btHeightfieldTerrainShape.h"

void im4MultArray4(Isgl3dMatrix4 * m, float * array) {
	float ax = array[0];
	float ay = array[1];
	float az = array[2];
	float aw = array[3];
	
	float vx = m->sxx * ax + m->sxy * ay + m->sxz * az + m->tx * aw;
	float vy = m->syx * ax + m->syy * ay + m->syz * az + m->ty * aw;
	float vz = m->szx * ax + m->szy * ay + m->szz * az + m->tz * aw;
	float vw = m->swx * ax + m->swy * ay + m->swz * az + m->tw * aw;
    
	array[0] = vx;
	array[1] = vy;
	array[2] = vz;
	array[3] = vw;
}

void iv3Normalize(Isgl3dVector3 * a) {
	float length = iv3Length(a);
	
	if (length != 0.0 && length != 1.0) {
		length = 1.0 / length; // Save some CPU
		a->x *= length;
		a->y *= length;
		a->z *= length;
	}
}

//=======================================================
// World generate
// RSWorldChunk
//=======================================================
@implementation RSWorldChunk

- (id)init:(RSWorld*)p worldx:(float)x worldz:(float)z {
    [super init];
    
    waterAltitude = -180.0;
    self->parent = p;

    float chunkWidth = p->chunkWidth;
    float cells = p->cellsPerChunk;

    //Create terrain mesh  
    static Isgl3dCustomShader * terrainShader = [TerrainShader shaderWithKey:@"terrainShader"];
    static Isgl3dShaderMaterial * terrainShaderMaterial = [Isgl3dShaderMaterial materialWithShader:terrainShader];
    
    terrainMesh = [Isgl3dTerrainMeshRandom create:chunkWidth depth:chunkWidth nx:cells nz:cells worldx:x worldz:z];
    terrain = [Isgl3dMeshNode nodeWithMesh:terrainMesh andMaterial:terrainShaderMaterial];
    terrain.position = iv3(x, 0, z);
    [self addChild:terrain];
    
    // And its physics model
	terrainShape = new btHeightfieldTerrainShape(cells+1, cells+1, terrainMesh->terrainHeightData, 1.0, -1000, 1000, 1, PHY_FLOAT, false);
	terrainShape->setLocalScaling(btVector3(p->cellUnitWidth, 1.f, p->cellUnitWidth));
    
    // add to physics world
    terrainPhysics = [p createPhysicsObject:terrain shape:terrainShape mass:0 restitution:0.6 isFalling:NO];
    btVector3 phyPos = btVector3(chunkWidth/2, 0, chunkWidth/2);
    terrainPhysics.rigidBody->translate(phyPos);
    
    // Create other objects on the terrain.
    objects = [self createNode];
    
    [self generateObjects];
    return self;
}
- (void)dealloc {
    //delete terrainShape;
    [super dealloc];
}
- (void) generateObjects{
    // Flat shader for those flat model
    static Isgl3dCustomShader * flatShader = [FlatShader shaderWithKey:@"flatShader"];
    static Isgl3dShaderMaterial * flatShaderMaterial = [Isgl3dShaderMaterial materialWithShader:flatShader];
    
    // Flat hitable objects
    static btVector3 btBox = btVector3(100, 50, 1);
    static btCollisionShape* btFlat = new btSphereShape(50);
    
    // objects
    static Isgl3dGLTextureFactory* tf = [Isgl3dGLTextureFactory sharedInstance];
    
    static Isgl3dGLTexture* aquatic[] = {
        [tf createTextureFromFile:@"aquatic/aquaticPlant1.png" precision:Isgl3dTexturePrecisionMedium repeatX:NO repeatY:NO],
        [tf createTextureFromFile:@"aquatic/aquaticPlant2.png" precision:Isgl3dTexturePrecisionMedium repeatX:NO repeatY:NO]
    };
    static Isgl3dGLTexture* cloud[] = {
        [tf createTextureFromFile:@"cloud3.png" precision:Isgl3dTexturePrecisionMedium repeatX:NO repeatY:NO],
    };
    static Isgl3dGLTexture* grass[] = {
        [tf createTextureFromFile:@"grass/grass1.png" precision:Isgl3dTexturePrecisionMedium repeatX:NO repeatY:NO],
        [tf createTextureFromFile:@"grass/grass2.png" precision:Isgl3dTexturePrecisionMedium repeatX:NO repeatY:NO],
        [tf createTextureFromFile:@"grass/grass3.png" precision:Isgl3dTexturePrecisionMedium repeatX:NO repeatY:NO],
        [tf createTextureFromFile:@"grass/grass4.png" precision:Isgl3dTexturePrecisionMedium repeatX:NO repeatY:NO],
        [tf createTextureFromFile:@"grass/grass5.png" precision:Isgl3dTexturePrecisionMedium repeatX:NO repeatY:NO],
        [tf createTextureFromFile:@"grass/grass6.png" precision:Isgl3dTexturePrecisionMedium repeatX:NO repeatY:NO],
        [tf createTextureFromFile:@"grass/grass7.png" precision:Isgl3dTexturePrecisionMedium repeatX:NO repeatY:NO],
        [tf createTextureFromFile:@"grass/grass8.png" precision:Isgl3dTexturePrecisionMedium repeatX:NO repeatY:NO],   
    };   
    static Isgl3dGLTexture* tree[] = {
        [tf createTextureFromFile:@"tree/tree1.png" precision:Isgl3dTexturePrecisionMedium repeatX:NO repeatY:NO],
        [tf createTextureFromFile:@"tree/tree10.png" precision:Isgl3dTexturePrecisionMedium repeatX:NO repeatY:NO],
        //[tf createTextureFromFile:@"tree/tree11.png" precision:Isgl3dTexturePrecisionMedium repeatX:NO repeatY:NO],
        [tf createTextureFromFile:@"tree/tree13.png" precision:Isgl3dTexturePrecisionMedium repeatX:NO repeatY:NO],
    };    
    static Isgl3dGLTexture* collectable[] = {
        [tf createTextureFromFile:@"cloud3.png" precision:Isgl3dTexturePrecisionMedium repeatX:NO repeatY:NO],
    };
    
    static int objectGenChance[] = {20, 28, 40, 43, 100};
    static int typeCount = (sizeof objectGenChance) / (sizeof objectGenChance[0]);
    
    // generate
    int mo = 70; //arc4random() % 100;
    for (int i=0; i<mo; i++) {
        int hx = arc4random() % ((int)terrainMesh->_width);
        int hz = arc4random() % ((int)terrainMesh->_depth);
        
        float cy = [terrainMesh getHeight:hx z:hz];
        float cx = terrainMesh->wx + (int)(hx/terrainMesh->_dx)*terrainMesh->_dx;
        float cz = terrainMesh->wz + (int)(hz/terrainMesh->_dz)*terrainMesh->_dz;
              
        Isgl3dPlane* omesh;
        Isgl3dMeshNode* o;
        
        int objectTypeSelector;// = arc4random() % 5;
        for (int it=0; it<typeCount; it++) {
            float a = i/(float)mo, b = objectGenChance[it]/100.;
            if (a<b) {
                objectTypeSelector = it;
                break;
            }
        }
        
        // If under water
        if(cy<waterAltitude - 30) {
            objectTypeSelector = 0;
        }
        else if(0==objectTypeSelector) continue;
        else if(cy<waterAltitude) continue;
        
        // a few parameters for generation control
        int objsCount;
        int randomSelector;
        Isgl3dGLTexture* textureSelected;
        float scale;
        float offsetY;
        
        switch (objectTypeSelector) {
            case 0:     //aquatic;
                objsCount = (sizeof aquatic)/(sizeof aquatic[0]);
                randomSelector = arc4random() % objsCount;
                textureSelected = aquatic[randomSelector];
                scale = (textureSelected.height / (waterAltitude - cy - 20)) + (arc4random() % 8);
                break;
            case 1:     //cloud;
                objsCount = (sizeof cloud)/(sizeof cloud[0]);
                randomSelector = arc4random() % objsCount;
                textureSelected = cloud[randomSelector];
                scale = 1.0f / (float)((arc4random() % 4) + 1);
                break;
            
            case 2:  default:    //grass;
                objsCount = (sizeof grass)/(sizeof grass[0]);
                randomSelector = arc4random() % objsCount;
                textureSelected = grass[randomSelector];
                scale = (arc4random() % 6) + 4;
                break;
            case 3:     //tree;
                objsCount = (sizeof tree)/(sizeof tree[0]);
                randomSelector = arc4random() % objsCount;
                textureSelected = tree[randomSelector];
                scale = (arc4random() % 4) + 1;
                break;
            case 4:     //collectable;
                objsCount = (sizeof collectable)/(sizeof collectable[0]);
                randomSelector = arc4random() % objsCount;
                textureSelected = collectable[randomSelector];
                scale = 20;
                break;
        }
        offsetY = (textureSelected.height/2 - (textureSelected.height - textureSelected.contentSize.height))/scale;
        
        omesh = [Isgl3dPlane meshWithGeometry:textureSelected.width/scale height:textureSelected.height/scale nx:1 ny:1];
        o = [objects createNodeWithMesh:omesh andMaterial:flatShaderMaterial];

        NSNumber* fuckingNSNumber = [[NSNumber alloc] initWithInt:objectTypeSelector];
        [o.tag addObject:textureSelected];
        [o.tag addObject:fuckingNSNumber];
        [fuckingNSNumber release];
    
        o.transparent = YES;
        o.rotationY = 180;
        o.position = iv3(cx, cy + offsetY, cz + (arc4random() % (int)terrainMesh->_dz));
        
        Isgl3dAction* actionCollectable;
        switch (objectTypeSelector) {
            case 0:     //aquatic
                o.y -= 0;
                break;
            case 1:     //cloud
                o.y = (arc4random()%10)*100 + 600;
                actionCollectable = 
                [Isgl3dActionRepeatForever actionWithAction:
                   [Isgl3dActionMoveBy actionWithDuration:(arc4random()%20)+6 vector:iv3(-1000, 0, 0)]
                ];
                [o runAction:actionCollectable]; 
                break;
            case 2:     //grass
                o.y -= 0;
                break;
            case 3:     //tree
                o.y -= 20;
                break;
            case 4:     //collectable;
                o.y += 0;
                actionCollectable = 
                [Isgl3dActionRepeatForever actionWithAction:
                    [Isgl3dActionParallel actionWithActions:
                        [Isgl3dActionRotateYBy actionWithDuration:1.0f angle:4*180.0f],
                        [Isgl3dActionSequence actionWithActions:
                            [Isgl3dActionMoveBy actionWithDuration:0.5f vector:iv3(0,3,0)],
                            [Isgl3dActionMoveBy actionWithDuration:0.5f vector:iv3(0,-3,0)],
                        nil],
                    nil]
                 ];
                o.doubleSided = YES;
                [o runAction:actionCollectable];
                break;
            default:
                break;
        }
    }
    
    // water plane
    float w = parent->chunkWidth;    
    //Isgl3dTextureMaterial * wm = [Isgl3dTextureMaterial materialWithTextureFile:@"water.jpg" shininess:0.5 precision:Isgl3dTexturePrecisionMedium repeatX:YES repeatY:YES];
    Isgl3dColorMaterial * wm = [Isgl3dColorMaterial materialWithHexColors:@"6699EE77" diffuse:@"33669977" specular:@"6699EE77" shininess:0.3];
    Isgl3dPlane* wmesh = [Isgl3dPlane meshWithGeometry:w height:w nx:4 ny:4];
    Isgl3dMeshNode* wmeshNode = [self createNodeWithMesh:wmesh andMaterial:wm];
    wmeshNode.doubleSided = YES;
    wmeshNode.alpha = 0.77;
    water = wmeshNode;
    water.rotationX = -90;
    water.position = iv3(terrainMesh->wx, waterAltitude, terrainMesh->wz);
    
    // clouds 
    /*Isgl3dTextureMaterial* cm = [Isgl3dTextureMaterial materialWithTextureFile:@"cloud3.png"];
    Isgl3dPlane* cmesh = [Isgl3dPlane meshWithGeometry:cm.width height:cm.height nx:4 ny:4];
    Isgl3dMeshNode* cmeshNode = [self createNodeWithMesh:cmesh andMaterial:cm];
    cmeshNode.alpha = 0.9;
    cmeshNode.transparent = YES;
    cmeshNode.rotationY = 180;
    cmeshNode.position = iv3(terrainMesh->wx, 800, terrainMesh->wz+31);*/
}

@end


//=======================================================
// World generate
// RSWorld
//=======================================================
@implementation RSWorld

- (id)init {
    [super init];
    
    cellsPerChunk = 6;
    cellUnitWidth = 200.0f;
    
    chunkWidth = cellUnitWidth * cellsPerChunk;
    viewRadius = chunkWidth * 1;                        //view distance is N times of chunkWidth.

    currentChunks = [[NSMutableDictionary alloc] init];
    
    // Create physics world with discrete dynamics
    _collisionConfig = new btDefaultCollisionConfiguration();
    _broadphase = new btDbvtBroadphase();
    _collisionDispatcher = new btCollisionDispatcher(_collisionConfig);
    _constraintSolver = new btSequentialImpulseConstraintSolver;
    btDiscreteDynamicsWorld* __discreteDynamicsWorld = new btDiscreteDynamicsWorld(_collisionDispatcher, _broadphase, _constraintSolver, _collisionConfig);
    
    // Gravity !
    __discreteDynamicsWorld->setGravity(btVector3(0,-30,0));
    [self setDiscreteDynamicsWorld:__discreteDynamicsWorld];    
    
    
    return self;
}

- (void) dealloc{
    [currentChunks release];
    [super dealloc];
}

- (void) refreshView:(Isgl3dNode*) centerTarget{
    
    float cx = centerTarget.x, cz = centerTarget.z;
    
    float left = cx + viewRadius, far = cz + viewRadius, right = cx - viewRadius, near = cz-viewRadius;    
    float chunkLeft = ((int)(left / chunkWidth)) * chunkWidth;
    float chunkFar = ((int)(far / chunkWidth) + 1) * chunkWidth;
    float chunkRight = ((int)(right / chunkWidth - 1)) * chunkWidth;
    float chunkNear = ((int)(near / chunkWidth)) * chunkWidth;
    
    for (float z=chunkNear; z<=chunkFar; z+=chunkWidth) {
        for (float x=chunkRight; x<=chunkLeft; x+=chunkWidth) {
            NSString* key = [NSString stringWithFormat:@"%d,%d",(int)x,(int)z];
            
            id exist = [currentChunks objectForKey:key];
            if (!exist){
                //chunk not exist
                RSWorldChunk* newChunk = [[RSWorldChunk alloc] init:self worldx:x worldz:z];
                //add to 3d scene
                [self addChild:newChunk];
                //add to looking table;
                [currentChunks setObject:key forKey:key];
                
                [newChunk release];
            }
        }
    }
   
    //delete those chunks out of the view.
    NSMutableArray* garbg = [[NSMutableArray alloc] init];
    
    float r2 = viewRadius * viewRadius;
    for (RSWorldChunk* c in self.children) {
        int wx = c->terrainMesh->wx, wz = c->terrainMesh->wz;
        float x = cx-wx, z = cz-wz;
        
        // 2nd condition means You can't delete chunks in front of the snail.
        if(x*x+z*z > r2 && wz<(cz-chunkWidth)){
            [garbg addObject:c];
            NSString* k = [NSString stringWithFormat:@"%d,%d",wx,wz];
            [currentChunks removeObjectForKey:k];
        }
    }
    
    //collect chunks
    for (RSWorldChunk*c in garbg) {
        [self removePhysicsObject:c->terrainPhysics];
        [self removeChild:c];
    }
    [garbg release];
}

- (Isgl3dPhysicsObject3D *) createPhysicsObject:(Isgl3dMeshNode *)node shape:(btCollisionShape *)shape mass:(float)mass restitution:(float)restitution isFalling:(BOOL)isFalling {
    
	if (isFalling) {
		[node setPositionValues:10 - (20.0 * random() / RAND_MAX) y:-60 z:10 - (20.0 * random() / RAND_MAX)];
	}
    
	Isgl3dMotionState * motionState = new Isgl3dMotionState(node);
	
	btVector3 localInertia(0, 0, 0);
	shape->calculateLocalInertia(mass, localInertia);
	btRigidBody * rigidBody = new btRigidBody(mass, motionState, shape, localInertia);
	rigidBody->setRestitution(restitution);
    
	Isgl3dPhysicsObject3D * physicsObject = [[Isgl3dPhysicsObject3D alloc] initWithNode:node andRigidBody:rigidBody];
	[self addPhysicsObject:physicsObject];
    	
	return [physicsObject autorelease];
}

@end


//=======================================================
// Terrain Shader		
//=======================================================  

#define MATERIAL_TEXTUREINDEX 0
#define BUMP_TEXTUREINDEX 1

@implementation TerrainShader

+ (id) shaderWithKey:(NSString *)key {
	return [[[self alloc] initWithKey:key] autorelease];
}

- (id) initWithKey:(NSString *)key {
	if ((self = [super initWithVertexShaderFile:@"terrainShader.vsh" fragmentShaderFile:@"terrainShader.fsh" key:key])) {
        
		_fAnim = 0.0f;
		
		// Create textures
		//_materialTexture = [[[Isgl3dGLTextureFactory sharedInstance] createTextureFromFile:@"brick.jpg" precision:Isgl3dTexturePrecisionMedium repeatX:YES repeatY:YES] retain];
		//_bumpTexture = [[[Isgl3dGLTextureFactory sharedInstance] createTextureFromFile:@"brick_bump.jpg" precision:Isgl3dTexturePrecisionMedium repeatX:YES repeatY:YES] retain];
        
		// Set the texture units to use
		//[self setUniformSamplerWithName:@"material" forTextureUnit:MATERIAL_TEXTUREINDEX];
		//[self setUniformSamplerWithName:@"bump" forTextureUnit:BUMP_TEXTUREINDEX];
        [self setUniform1fWithName:@"waterAltitude" value:-180];
	}
	return self;
}

- (void) dealloc {
	//[_materialTexture release];
	//[_bumpTexture release];
    
	[super dealloc];
}

- (void) onRenderPhaseBeginsWithDeltaTime:(float)dt {
	// Update the animation factor with a new render phase
	_fAnim += dt * 0.05;
	if (_fAnim > 0.5f) {
		_fAnim = 0.0f;
	}
	//[self setUniform1fWithName:@"fAnim" value:_fAnim];
}

- (void) onSceneRenderReady {
	// Update lighting (use only first light)
	if ([_lights count] > 0) {
		Isgl3dLight * light = [self.lights objectAtIndex:0]; 
        
		float lightPosition[4];
		[light copyWorldPositionToArray:lightPosition];
        im4MultArray4(self.viewMatrix, lightPosition);
		[self setUniform3fWithName:@"lightDirection" values:lightPosition];
	}
}

- (void) onModelRenderReady {
	
	// bind the vertex data to the attributes
	[self setVertexAttribute:GL_FLOAT attributeName:@"inVertex" size:VBO_POSITION_SIZE strideBytes:self.vboData.stride offset:self.vboData.positionOffset];
	[self setVertexAttribute:GL_FLOAT attributeName:@"inNormal" size:VBO_NORMAL_SIZE strideBytes:self.vboData.stride offset:self.vboData.normalOffset];
    
	// Set the mvp matrix
	[self setUniformMatrix4WithName:@"mvpMatrix" matrix:self.modelViewProjectionMatrix];
    
	// Set the modelview matrix
	[self setUniformMatrix4WithName:@"mvMatrix" matrix:self.modelViewMatrix];
	
	// Bind the textures to the texture unit	
	//[self bindTexture:_materialTexture textureUnit:MATERIAL_TEXTUREINDEX];
	//[self bindTexture:_bumpTexture textureUnit:BUMP_TEXTUREINDEX];
}

@end

//=======================================================
// Toon Shader		
//=======================================================  

@implementation ToonShader

+ (id) shaderWithKey:(NSString *)key {
	return [[[self alloc] initWithKey:key] autorelease];
}

- (id) initWithKey:(NSString *)key {
	if ((self = [super initWithVertexShaderFile:@"toonShader.vsh" fragmentShaderFile:@"toonShader.fsh" key:key])) {
		_materialTexture = [[[Isgl3dGLTextureFactory sharedInstance] createTextureFromFile:@"brick.jpg" precision:Isgl3dTexturePrecisionMedium repeatX:YES repeatY:YES] retain];
		_bumpTexture = [[[Isgl3dGLTextureFactory sharedInstance] createTextureFromFile:@"brick_bump.jpg" precision:Isgl3dTexturePrecisionMedium repeatX:YES repeatY:YES] retain];
        
        // Set the texture units to use
		//[self setUniformSamplerWithName:@"material" forTextureUnit:MATERIAL_TEXTUREINDEX];
		//[self setUniformSamplerWithName:@"bump" forTextureUnit:BUMP_TEXTUREINDEX];
 	}
	return self;
}

- (void) dealloc {
	[_materialTexture release];    
    [super dealloc];
}

- (void) onRenderPhaseBeginsWithDeltaTime:(float)dt {
}

- (void) onSceneRenderReady {
	// Update lighting (use only first light)
	if ([_lights count] > 0) {
		Isgl3dLight * light = [self.lights objectAtIndex:0]; 
        
		float lightPosition[4];
		[light copyWorldPositionToArray:lightPosition];
        im4MultArray4(self.viewMatrix, lightPosition);
		[self setUniform3fWithName:@"lightDirection" values:lightPosition];
	}
}

- (void) onModelRenderReady {
	
	// bind the vertex data to the attributes
	[self setVertexAttribute:GL_FLOAT attributeName:@"inVertex" size:VBO_POSITION_SIZE strideBytes:self.vboData.stride offset:self.vboData.positionOffset];
	[self setVertexAttribute:GL_FLOAT attributeName:@"inNormal" size:VBO_NORMAL_SIZE strideBytes:self.vboData.stride offset:self.vboData.normalOffset];
	//[self setVertexAttribute:GL_FLOAT attributeName:@"inUV" size:VBO_UV_SIZE strideBytes:self.vboData.stride offset:self.vboData.uvOffset];
    
	// Set the mvp matrix
	[self setUniformMatrix4WithName:@"mvpMatrix" matrix:self.modelViewProjectionMatrix];
    
	// Set the modelview matrix
	[self setUniformMatrix3WithName:@"mvMatrix" matrix:self.modelViewMatrix];
    
	//[self bindTexture:_materialTexture textureUnit:MATERIAL_TEXTUREINDEX];
	//[self bindTexture:_bumpTexture textureUnit:BUMP_TEXTUREINDEX];
}


@end
//=======================================================
// Flat Shader		
//=======================================================
@implementation FlatShader

+ (id) shaderWithKey:(NSString *)key {
	return [[[self alloc] initWithKey:key] autorelease];
}

- (id) initWithKey:(NSString *)key {
	if ((self = [super initWithVertexShaderFile:@"flatShader.vsh" fragmentShaderFile:@"flatShader.fsh" key:key])) {
        // Set the texture units to use
		[self setUniformSamplerWithName:@"material" forTextureUnit:MATERIAL_TEXTUREINDEX];
 	}
	return self;
}

- (void) dealloc {
    [super dealloc];
}

- (void) onRenderPhaseBeginsWithDeltaTime:(float)dt {
    // Update the animation factor with a new render phase
	waveOffset = sin(dt * 0.05);
	//[self setUniform1fWithName:@"waveOffset" value:waveOffset];
}

- (void) onSceneRenderReady {
	// Update lighting (use only first light)
	if ([_lights count] > 0) {
		Isgl3dLight * light = [self.lights objectAtIndex:0]; 
        
		float lightPosition[4];
		[light copyWorldPositionToArray:lightPosition];
        im4MultArray4(self.viewMatrix, lightPosition);
        
        Isgl3dVector3 li = iv3(lightPosition[0], lightPosition[1], lightPosition[2]);
        iv3Normalize(&li);
        Isgl3dVector3 normalPlat = iv3(0, 1, 0);
        float intense = fmaxf(iv3Dot(&li, &normalPlat), 0.0f);
        
        [self setUniform1fWithName:@"fLightIntensity" value:intense];
	}
    

}

- (void) onModelRenderReady {
	
	// bind the vertex data to the attributes
	[self setVertexAttribute:GL_FLOAT attributeName:@"inVertex" size:VBO_POSITION_SIZE strideBytes:self.vboData.stride offset:self.vboData.positionOffset];
	//[self setVertexAttribute:GL_FLOAT attributeName:@"inNormal" size:VBO_NORMAL_SIZE strideBytes:self.vboData.stride offset:self.vboData.normalOffset];
	[self setVertexAttribute:GL_FLOAT attributeName:@"inUV" size:VBO_UV_SIZE strideBytes:self.vboData.stride offset:self.vboData.uvOffset];
    
	// Set the mvp matrix
	[self setUniformMatrix4WithName:@"mvpMatrix" matrix:self.modelViewProjectionMatrix];
    // Set the modelview matrix
	[self setUniformMatrix4WithName:@"mvMatrix" matrix:self.modelViewMatrix];

    // Set texture
    //Isgl3dTextureMaterial* tm = (Isgl3dTextureMaterial*)self.activeNode.tag;
    Isgl3dGLTexture* t = [self.activeNode.tag objectAtIndex:0];
	[self bindTexture:t textureUnit:MATERIAL_TEXTUREINDEX];
    
    // Set wave
    static float angle = 0;
    angle += 0.0002;
    float v = 0.;
    
    NSNumber* objType = [self.activeNode.tag objectAtIndex:1];
    if (objType.intValue==2 || objType.intValue==0) {
        v = sin(angle)*cos(angle) * 12;
    }
    [self setUniform1fWithName:@"fWaveOffset" value:v];
}


@end
//=======================================================
// Application view	
//=======================================================  
@implementation Isgl3dTutorial1View

- (id) init {
	if ((self = [super init])) {
        //=======================================================
		// Main config		
        //=======================================================        
        // Schedule updates
		[self schedule:@selector(tick:)];
        self.sceneAmbient = @"555555";
        
		// Enable shadows
		[Isgl3dDirector sharedInstance].shadowRenderingMethod = Isgl3dShadowPlanar;
		[Isgl3dDirector sharedInstance].shadowAlpha = 0.5;
		    
        //=======================================================
        // Generate world
        //=======================================================
        world = [[RSWorld alloc] init];
        [self.scene addChild:world];
        [world release];
        
        //=======================================================
        // Create sky sphere
        //=======================================================
		//sky = [self.scene createNode];
		Isgl3dTextureMaterial * skyMaterial = [Isgl3dTextureMaterial materialWithTextureFile:@"violentdays.jpg"];
		Isgl3dCubeSphere * skyMesh = [Isgl3dCubeSphere meshWithGeometry:2500.0 longs:4 lats:4];
		Isgl3dMeshNode* _sky = [sky createNodeWithMesh:skyMesh andMaterial:skyMaterial];
        _sky.doubleSided = YES;
		_sky.lightingEnabled = NO;
        
        //=======================================================
		// Create snail		
        //=======================================================
		// Create a ball to present the snail
		Isgl3dTextureMaterial * checkerMaterial = [Isgl3dTextureMaterial materialWithTextureFile:@"red_checker.png" shininess:0.9 precision:Isgl3dTexturePrecisionMedium repeatX:NO repeatY:NO];
        //Isgl3dCustomShader* shaderMaterial = [TerrainShader shaderWithKey:@"snailShader"];
        //Isgl3dShaderMaterial* toonShaderMaterial = [Isgl3dShaderMaterial materialWithShader:shaderMaterial];
		Isgl3dSphere * sphereMesh = [Isgl3dSphere meshWithGeometry:2.0 longs:10 lats:10];
		snail = [self.scene createNodeWithMesh:sphereMesh andMaterial:checkerMaterial];
		snail.position = iv3(0, 60, 0);
		snail.enableShadowCasting = YES;
        snail.interactive = YES;
        snail.isPlanarShadowsNode = YES;
        [snail addEvent3DListener:self method:@selector(snailTouched:) forEventType:TOUCH_EVENT];
        
        //[world createPhysicsObject:snail shape:new btSphereShape(sphereMesh.radius) mass:0.5 restitution:0.9 isFalling:YES]; 
        //[world refreshView:snail];
        
        //=======================================================    
        // Create follow camera
        //=======================================================
        _followCamera = [[Isgl3dFollowCamera cameraWithWidth:self.camera.width andHeight:self.camera.height andTarget:snail] retain];
		[_followCamera setPerspectiveProjection:45 near:1 far:1000 orientation:self.deviceViewOrientation];
		_followCamera.stiffness = 20;
		_followCamera.damping = 5;
		_followCamera.lookAhead = 20;
        
        _followCamera.position = iv3(0,20,0);
		[self.scene addChild:_followCamera];
        //self.camera = _followCamera;
        //self.camera.position = iv3(0, 10, -200);
        self.camera.far = 8000;
        followCameraPosition = iv3(0, 30, -60);
        followCameraLookAt = iv3(0, 10, 140);

        //((ToonShader*)toonShader)->eye = self.camera;
        //=======================================================
		// Add light
        //=======================================================
		light  = [Isgl3dShadowCastingLight lightWithHexColor:@"FFFFFF" diffuseColor:@"FFFFFF" specularColor:@"FFFFFF" attenuation:0.005];
		[self.scene addChild:light];
        
        //=======================================================
		// Add Skybox
        //=======================================================
        /*Isgl3dSphere* sky = [Isgl3dCubeSphere meshWithGeometry:6000 longs:40 lats:40];
        Isgl3dTextureMaterial* skyTexture = [Isgl3dTextureMaterial materialWithTextureFile:@"grimmnight_large.jpg" shininess:0.5 precision:Isgl3dTexturePrecisionMedium repeatX:NO repeatY:NO];
		Isgl3dNode* _sky = [self.scene createNodeWithMesh:sky andMaterial:skyTexture];
		_sky.position = iv3(0, 60, 0);*/
	}
	
	return self;
}

- (void) dealloc {
    // Release camera controller
	[_cameraController release];
    
	[super dealloc];
}


- (void) onActivated {
	// Add camera controller to touch-screen manager
	[[Isgl3dTouchScreen sharedInstance] addResponder:_cameraController];
}

- (void) onDeactivated {
	// Remove camera controller from touch-screen manager
	[[Isgl3dTouchScreen sharedInstance] removeResponder:_cameraController];
}

- (void) snailTouched:(Isgl3dEvent3D*)event {
    //Isgl3dPhysicsObject3D* o = event.object;
    //float f = arc4random() % 400 - 200;
    //[snailPhysics applyForce:iv3(0, 8000, -3000) withPosition:iv3(snail.x, snail.y+8000, snail.z-3000)];
    static btVector3 v = btVector3(0,0,1);
    snailPhysics.rigidBody->setLinearVelocity(v);
    [snailPhysics applyForce:iv3(0, 1000, 0) withPosition:iv3(snail.x, snail.y+1000, snail.z)];
}

- (void) tick:(float)dt {
    static bool start = true;
    if(start){
        snail.position = iv3(100, 130, 700);
        [world refreshView:snail];
        snailPhysics = [world createPhysicsObject:snail shape:new btSphereShape(1.0) mass:0.8 restitution:0.04 isFalling:NO]; 
        start = false;
    }

    // control snail's velocity
    btVector3 sv = snailPhysics.rigidBody->getLinearVelocity();
    if(sv.length()<200)
        [snailPhysics applyForce:iv3(0, 0, 40) withPosition:iv3(snail.x, snail.y, snail.z+40)];
    
    static float angle = 1.0;
    static float amp = 20;
    light.position = snail.position;
    light.x += amp*amp*cos(angle);
    light.y += amp*amp*sin(angle);
    light.z += amp;
    
    self.camera.position = iv3(snail.x+followCameraPosition.x,snail.y+followCameraPosition.y,snail.z+followCameraPosition.z);
    [self.camera setLookAt:iv3(snail.x+followCameraLookAt.x,snail.y+followCameraLookAt.y,snail.z+followCameraLookAt.z)];
    
    // generate new chunk when needed.
    static float lastPositionZ = snail.z;
    static float lastPositionX = snail.x;
    if(abs(snail.z - lastPositionZ) > world->chunkWidth / 4 || abs(snail.x - lastPositionX) > world->chunkWidth / 4 ){
        [world refreshView:snail];
        lastPositionZ = snail.z;
        lastPositionX = snail.x;
    }
    
    
    // Sunlight varying
    static float STEP = 0.0005, PI = 3.1415926, PI_2 = 2*PI;
    static float angleStep = STEP;

    angle += angleStep;
    if(angle>PI_2) {
        angleStep = STEP;
        angle = 0;
    }else if(angle>PI){
        angleStep = STEP * 4;
    };
    
    
    //NSLog(@"{%f3.6}", angle);
}

/*
 * Callback for touch event on 3D object
 */
- (void) objectTouched:(Isgl3dEvent3D *)event {
	// Update camera target
	_cameraController.target = event.object;
}

/*
 * Callback for action
 */
- (void) callback1 {
	NSLog(@"Callback1 called !");
}
@end


//=======================================================
// User Interface
//=======================================================
@implementation SnailRushUIView

@synthesize snailRush = _snailRush;

- (id) init {
	
	if ((self = [super init])) {
		// Create a left/right button
		Isgl3dTextureMaterial* buttonMaterial = [Isgl3dTextureMaterial materialWithTextureFile:@"cloud3.png" shininess:0.9 precision:Isgl3dTexturePrecisionMedium repeatX:NO repeatY:NO];
		Isgl3dGLUIButton* leftButton = [Isgl3dGLUIButton buttonWithMaterial:buttonMaterial];
		Isgl3dGLUIButton* rightButton = [Isgl3dGLUIButton buttonWithMaterial:buttonMaterial];
        
		[self.scene addChild:leftButton];
		[self.scene addChild:rightButton];

		[leftButton setX:8 andY:8];
		[rightButton setX:424 andY:8];
        
		[leftButton addEvent3DListener:self method:@selector(leftButtonPressed:) forEventType:TOUCH_EVENT];
		[rightButton addEvent3DListener:self method:@selector(rightButtonPressed:) forEventType:TOUCH_EVENT];
		[leftButton addEvent3DListener:self method:@selector(buttonReleased:) forEventType:RELEASE_EVENT];
		[rightButton addEvent3DListener:self method:@selector(buttonReleased:) forEventType:RELEASE_EVENT];        
	}
	
	return self;
}

- (void) dealloc {
    
	[super dealloc];
}

- (void) buttonReleased:(Isgl3dEvent3D *)event {
    Isgl3dGLUIButton* b = event.object;
    //b.x -= 0;
    //b.y += 0;
}
- (void) buttonPressed:(Isgl3dEvent3D *)event {
    Isgl3dGLUIButton* b = event.object;
    //b.x += 0;
    //b.y -= 0;
}

- (void) leftButtonPressed:(Isgl3dEvent3D *)event {
    [self buttonPressed:event];
	NSLog(@"Left button pressed");
    Isgl3dNode* s = _snailRush->snail;
    btVector3 f = btVector3(400.0, 0, 0);
    _snailRush->snailPhysics.rigidBody->applyCentralForce(f);
    [_snailRush.camera roll:45];
}
- (void) rightButtonPressed:(Isgl3dEvent3D *)event {
    [self buttonPressed:event];    
	NSLog(@"Right button pressed");
    Isgl3dNode* s = _snailRush->snail;
    btVector3 f = btVector3(-400.0, 0, 0);
    _snailRush->snailPhysics.rigidBody->applyCentralForce(f);    
}


@end
#pragma mark AppDelegate

/*
 * Implement principal class: simply override the createViews method to return the desired demo view.
 */
@implementation AppDelegate

- (void) createViews {
	// Set the device orientation
	[Isgl3dDirector sharedInstance].deviceOrientation = Isgl3dOrientationLandscapeLeft;

	// Create view and add to Isgl3dDirector
    Isgl3dTutorial1View* sview = [Isgl3dTutorial1View view];
	Isgl3dView * view = sview;
	[[Isgl3dDirector sharedInstance] addView:view];
    view.zSortingEnabled = YES;
    
    // Create UI and add to Isgl3dDirector
	SnailRushUIView * sui = [SnailRushUIView view];
    sui.snailRush = sview;
	Isgl3dView * ui = sui;
	[[Isgl3dDirector sharedInstance] addView:ui];

}

@end
